using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Diagnostics;


namespace NodeEditor
{
    public class VarData {
        public string type;
        public string key;
        public object value;

        public void ChangeValue(object obj) {
            value = obj;
        }
    }

    public abstract class Node : Border, IDisposable {
    	public delegate void NodeEventHandler(Node node);
        public event NodeEventHandler moved;

        public NodeGraphContext context => graph?.context;
        public InputDock[] getInputs() => Dispatcher.Invoke(() => stackInputs.Children.OfType<InputDock>().ToArray());
        public OutputDock[] getOutputs() => Dispatcher.Invoke(() => stackOutputs.Children.OfType<OutputDock>().ToArray());

        private static int genUid = 0;
        public int uid;

        private bool _isSelected;
        public bool IsSelected {
            get => _isSelected;
            set {
                _isSelected = value;
                BorderBrush = _isSelected ? Brushes.LightBlue : Brushes.White; // 淡白色或默认颜色
                BorderThickness = new Thickness(_isSelected ? 1 : 0); // 选中时加粗边框
            }
        }

        public Node() {
        	initialize();
        }

        // node name
        public string title {
            get {
                return titleUI.Text;
            }
            set {
                titleUI.Text = value;
            }
        }

        public string desc;

        private List<VarData> varList;

        public NodeGraph graph { get; private set; }

        private TextBlock titleUI;
        private StackPanel stackInputs;
        private StackPanel stackParameters;
        private StackPanel stackOutputs;
        private Grid grid;
        private Nullable<Point> dragStart = null;

        public Point position {
            get { return new Point(Canvas.GetLeft(this), Canvas.GetTop(this)); }
            set {
                Canvas.SetLeft(this, value.X);
                Canvas.SetTop(this, value.Y);
                moved?.Invoke(this);
            }
        }

        private void initialize() {
            genUid += 1;
            uid = genUid;

            MinHeight = 40;
            
            CornerRadius = new CornerRadius(5);
            var converter = new BrushConverter();
            Background = (Brush)converter.ConvertFromString("#262626");
            BorderBrush = Brushes.White;
            BorderThickness = new Thickness(0);

            Style = (Style)Application.Current.Resources["NodeStyle"];

            int padSize = 12;

            titleUI = new TextBlock {
                Foreground = Brushes.White,
                FontWeight = FontWeights.Bold,
                HorizontalAlignment = HorizontalAlignment.Center,
                Padding = new Thickness(padSize,0,padSize,0)
            };

            stackInputs = new StackPanel();
            stackParameters = new StackPanel { VerticalAlignment = VerticalAlignment.Center };
            stackOutputs = new StackPanel();

            PreviewMouseLeftButtonDown += Node_PreviewMouseLeftButtonDown;
            MouseDown += mouseDown;
            MouseMove += mouseMove;
            MouseUp += mouseUp;

            varList = new List<VarData>();
        }

        public abstract void setConnections();

        Point startPoint;

        private void Node_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
            graph?.SelectNode(this);
            startPoint = e.GetPosition(null);
        }

		private void mouseDown(object sender, MouseEventArgs args) {
            var element = (UIElement)sender;
            dragStart = args.GetPosition(element);
            element.CaptureMouse();
        }

		private void mouseUp(object sender, MouseEventArgs args) {
            var element = (UIElement)sender;
            dragStart = null;
            element.ReleaseMouseCapture();
        }

        private void mouseMove(object sender, MouseEventArgs args) {
            if (dragStart != null && args.LeftButton == MouseButtonState.Pressed) {
                UIElement element = (UIElement)sender;
                Point p2 = args.GetPosition(graph.canvas);
                position = new Point(p2.X - dragStart.Value.X, p2.Y - dragStart.Value.Y);
            }
        }

        protected override void OnVisualParentChanged(DependencyObject oldParent) {
            base.OnVisualParentChanged(oldParent);
            DependencyObject current = this;

            for (int i = 0; i < 5; i++) {
                current = (current as FrameworkElement)?.Parent;
                if (current == null)
                    break;
                graph = current as NodeGraph;
                if (graph != null)
                    break;
            }
            updateOrientation();
        }

        public void updateOrientation() {
            if (grid != null)
                grid.Children.Clear();

            grid = new Grid();

            grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
            grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(8) });
            grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(20) });
            grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
            grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(8) });

            stackInputs.Orientation = Orientation.Horizontal;
            stackInputs.Margin = new Thickness(0, -8, 0, 0);
            stackInputs.HorizontalAlignment = HorizontalAlignment.Center;
            stackOutputs.Margin = new Thickness(0, 0, 0, -8);
            stackOutputs.HorizontalAlignment = HorizontalAlignment.Center;

            Grid.SetRow(stackInputs, 0);
            Grid.SetRow(titleUI, 1);
            Grid.SetRow(stackOutputs, 3);

            Child = grid;

            grid.Children.Add(titleUI);
            grid.Children.Add(stackInputs);
            grid.Children.Add(stackOutputs);

            moved?.Invoke(this);
        }

        public Dock addInput(int type) {
            InputDock dock = new InputDock(this, type);
            stackInputs.Children.Add(dock);
            return dock;
        }

        public OutputDock addOutput(int type) {
            OutputDock dock = new OutputDock(this, type);
            stackOutputs.Children.Add(dock);
            return dock;
        }

        public void Dispose() {
            Visibility = Visibility.Collapsed;
            foreach (InputDock input in getInputs()) {
                // if (input.pipe != null)
                //     input.pipe.Dispose();
                foreach (Pipe pipe in input.pipes.ToArray()) {
                    pipe.Dispose();
                }
            }
            foreach (OutputDock output in getOutputs()) {
                foreach (Pipe pipe in output.pipes.ToArray()) {
                    pipe.Dispose();
                }
            }
        }

        public bool AddVar(string type, string name, out VarData varData) {
            return Extensions.AddVar(this.varList, type, name, out varData);
        }

        public void DelVar(string name) {
            Extensions.DelVar(this.varList, name);
        }

        public List<VarData> GetAllVar() {
            return this.varList;
        }

        public void LoadAllVar(List<VarData> varList) {
            if (varList != null) {
                this.varList = varList;
            }
        }

        public List<VarData> CopyNodeVarList() {
            List<VarData> copiedVarList = this.varList.Select(varData => new VarData {
                type = varData.type,
                key = varData.key,
                value = varData.value
            }).ToList();

            return copiedVarList;
        }
    }
}